# -*- coding: utf-8 -*-

from onlinebrary.lib.converter.converter import AbstractConverter

import onlinebrary.lib.converter.feedparser as feedparser
    
import xml.sax.handler
import xml.sax.saxutils as saxutils

from pylons import config

import time
import sys
import md5
import urllib2
import base64
import re
import locale
import codecs
import os
import zipfile
import logging
import socket
import Image
import ImageOps
import StringIO
import thread

log = logging.getLogger(__name__)

SVALKA = os.path.join(config['pylons.paths']['static_files'], 'svalka')
    
class FeedToFb2Converter(AbstractConverter):

    def __init__(self):
        self.fb2_header_template=u"""<?xml version="1.0" encoding="UTF-8"?>
        <FictionBook xmlns="http://www.gribuser.ru/xml/fictionbook/2.0" xmlns:xlink="http://www.w3.org/1999/xlink">
            <stylesheet xmlns="" type="text/css">
                body {
                    font-family : Verdana, Geneva, Arial, Helvetica, sans-serif;
                }
                p {
                    margin : 0.5em 0 0 0.3em;
                    padding : 0.2em;
                    text-align:justify;
                }
                code {
                    font-family : Courier, Verdana;
                }
                url {
                    text-decoration : underline;
                    font-style: italic;
                }
            </stylesheet>
            <description>
                <title-info>
                    <genre>RssFeeds</genre>
                    <book-title>%s</book-title>
                    <lang>ru</lang>
                </title-info>
            </description>
            <body>"""
        self.fb2_footer = u"</body></FictionBook>"

    def convert(self, url, zipped=True):
        log.info("Converting %s", url)
        self.enable_pics = 1            # by default image processing is enabled
        self.enable_zip = zipped        # by default GZIPping is enabled
        self.get_all_posts = 0
        try:
            book_title, feed, feed_images = self.feedToSection(url)
        except Exception, e:
            log.error("Error occurred during feed procession url = %s\nError: %s", url, e)
            return None
        else:
            if book_title is None and feed is None:
                log.error('No feed title. No feed body. Exiting')
                return None
            if len(feed) == 0:
                log.warn("Feed is empty. Because of old posts or because of absence of any posts. Passing by url = %s", url)
                return None
            #feedtitle_encoded = book_title.encode('utf8')
            #feed = unicode(feed, 'utf8').encode('utf8')
            book_body = (self.fb2_header_template % book_title) + feed
        
        if self.enable_pics:
            book_body += self.imagesToSections(feed_images)
        
        book_body += self.fb2_footer

        try:
            filename = self.write_book_file(SVALKA, book_title, book_body, self.enable_zip)
        except Exception, e:
            log.error("Error when writing file: %s", e)
        else:
            log.info("Convertation is finished. File %s created", filename)
            return filename
            
        return None

    def cleanPostBody(self, text):

        offset = 0
        pos = 0
        result_text = ""
        
        buff_open = []
        attr = None
            
        while offset < len(text):
            tag_opened = re.search("<(?P<tag>[a-zA-Z\-]+)(?P<attr>.*?)>", text[offset:], re.M | re.I | re.S)
            tag_closed = re.search("</(?P<tag>[a-zA-Z]+)\s*>", text[offset:], re.M | re.I | re.S)
            
            buff = ""
            
            if tag_opened and tag_closed:
                first_start = min(tag_opened.start(), tag_closed.start())

            if tag_opened and ((not tag_closed) or first_start == tag_opened.start()) :
                start = offset + tag_opened.start()
                pos = offset + tag_opened.end()
                
                tag = tag_opened.group('tag')
                attr = tag_opened.group('attr')

                if not attr: attr = ""
             
                if tag in ["img", "br", "hr"] :
                    buff = "<%s%s/>" % (tag, attr)
                elif attr == "/" or attr[-2:] == ' /':
                    buff = "<%s/>" % tag
                else:
                    buff = "<%s%s>" % (tag, attr)
                    buff_open.append(tag)

            if tag_closed and ((not tag_opened) or first_start == tag_closed.start()) :
                start = offset + tag_closed.start()
                pos = offset + tag_closed.end()
                
                tag = tag_closed.group('tag')
                
                if tag in buff_open:
                    buff_open.reverse()
                    for i in range(0, buff_open.index(tag) + 1):
                        buff += "</%s>" % buff_open.pop(0)
                    buff_open.reverse()
                else:
                    buff = ""

            if not tag_closed and not tag_opened:
                start = len(text)
                pos = len(text)
            result_text += text[offset:start] + buff
            offset = pos

        if len(buff_open):
            buff_open.reverse()
            for tag in buff_open:
                result_text += "</%s>" % tag
            
        return result_text



    def postToSection(self, entry):

        post_body_name = ""
        if entry.has_key("title"):
            title = entry["title"]
        else:
            title = u"untitled post"

        if entry.has_key('content'):
            post_body_name = "content"
            itemblock = entry[post_body_name][0]['value']
        elif entry.has_key('summary'):
            post_body_name = "summary"
            itemblock = entry[post_body_name]
        elif entry.has_key('description'):
            post_body_name = "description"
            itemblock = entry[post_body_name]
        else:
            return "", {}

        itemblock = self.cleanPostBody(self.htmlTagsToFb2Tags(itemblock)) 

        itemblock = '<title><p>' + title + '</p></title>\n<p>' + itemblock + '</p>'

        images = {}

        log.debug("Going to change all image urls")

        img_tag_pattern = '<img[^>]*src\s*=\s*("|\')?(?P<url>[^\'">]*)("|\s|\')[^>]*[/]?>'
        if self.enable_pics:
            while re.search(img_tag_pattern, itemblock, re.M | re.I | re.S) is not None:
                matchobj = re.search(img_tag_pattern, itemblock, re.M | re.I | re.S)
                img_url = matchobj.group('url')
                digest = md5.new(img_url).hexdigest()
                images[digest] = img_url
                itemblock = itemblock.replace(itemblock[matchobj.start():matchobj.end()], (u"<image xlink:href=\"#%s\"/>" % digest))
                log.debug("URL %s catched and changed", img_url)
        else:
            img_alttext_pattern = '<img[^>]*?(alt\s*=\s*("|\')?(?P<alt>[^\'">]*)(\'|"|\s)?)[^>]*?[/]?>'
            while re.search(img_alttext_pattern, itemblock, re.M | re.I | re.S) is not None:
                matchobj = re.search(img_alttext_pattern, itemblock, re.M | re.I | re.S)
                alt = matchobj.group('alt')
                if alt:
                    itemblock = itemblock.replace(itemblock[matchobj.start():matchobj.end()], (u"[%s]" % alt))
                else:
                    matchobj = re.search(img_tag_pattern, itemblock, re.M | re.I | re.S)
                    img_url = matchobj.group('url')
                    if img_url:
                        itemblock = itemblock.replace(itemblock[matchobj.start():matchobj.end()], (u"[%s]" % img_url))
                    else:
                        itemblock = itemblock.replace(itemblock[matchobj.start():matchobj.end()], (u"[image]"))

            while re.search(img_tag_pattern, itemblock, re.M | re.I | re.S) is not None:
                matchobj = re.search(img_tag_pattern, itemblock, re.M | re.I | re.S)
                img_url = matchobj.group('url')
                if img_url:
                    itemblock = itemblock.replace(itemblock[matchobj.start():matchobj.end()], ("u[%s]" % img_url))
                else:
                    itemblock = itemblock.replace(itemblock[matchobj.start():matchobj.end()], ("u[image]"))

        log.debug("Image links processing finished")
        return itemblock, images


    def feedToSection(self, url):

        log.debug("Converting feed to section. %s", url)

        url = url.rstrip("\n")
        
        #if self.get_all_posts :
        modified_since = None
        #elif not url_last_update.has_key(url):
        #    modified_since = None
        #else :
        #    modified_since = url_last_update[url]
        
        #if not modified_since is None:
        #    print "\t[since %s]" % time.strftime("%b %d %Y %H:%M:%S", modified_since),
        
        feed = feedparser.parse(url) #, modified=modified_since)
        #if feed.has_key('status') and feed['status'] == 304:
        #    " is not modified since last check"
        #    return None, None, None
        
        if feed.version == "":
            log.error("Cannot connect to the server or unknown feed type at %s", url)
            return None
        if feed['feed'].has_key('title'):
            feedtitle = feed['feed']['title']
        else :
            feedtitle = "Untitled feed" 
        
        log.debug("Converting feed title is %s", feedtitle)
            
        feedblock = ""
        feed_images = {}
        i = 0
        for entry in feed['entries']:
            if not self.get_all_posts and modified_since:
                updated = entry.updated_parsed
                if updated < modified_since :
                    # we assuming that if this post is outdated then next posts are outdated too
                    #print "Post is outdated. Breaking out"
                    break
            try:
                post_block, post_images = self.postToSection(entry)
            except Exception, e:
                log.error("Error during post processing. Passing by. [title = %s, url = %s] %s", feedtitle, url, e)
            else:
                feedblock += u"<section>\n\t" + post_block + u"\n</section>\n"
                for key, value in post_images.items():
                    feed_images[key] = value
                i += 1
        
        log.info("%d posts converted in %s", i, url)
        return feedtitle, feedblock, feed_images


    def htmlTagsToFb2Tags(self, fb2content):
        log.debug("HTML tags processing. Length of content is %d", len(fb2content))
        
        # hacking around tags
        fb2content = re.sub('<\s*br\s*[/]?>\s*<\s*br\s*[/]?>', '<empty-line/>', fb2content)
        fb2content = re.sub('<\s*br\s*[/]?>', '<empty-line/>', fb2content)
        fb2content = re.sub('<[/]?pre>', '', fb2content)
        
        codetag = re.search("<code[^>]*>(?P<code>.*?)</code>", fb2content, re.M | re.I | re.S)
        if codetag :
            code = codetag.group('code')
            code = code.replace('>', '&gt;')
            code = code.replace('<', '&lt;')
            fb2content = fb2content[:codetag.start()] + "<code>" + code + "</code>" + fb2content[codetag.end():]
        
        #fb2content = re.sub('<a(?P<attrs>.*?)>', '<url\g<attrs>>', fb2content) 
        #fb2content = re.sub('</a>', '</url>', fb2content) 
        
        #fb2content = re.sub("<[/]?(code|span|div|b|i|em|strong)[^>]*>", "", fb2content)
        #fb2content = re.sub("<(ol|ul|li|span|b|i|em)[^>]*>", "<p>", fb2content)
        #fb2content = re.sub("</(ol|ul|li|span|b|i|em)>", "</p>", fb2content)

        fb2content = fb2content.replace("&rarr;", "--")
        fb2content = fb2content.replace("&larr;", "--")
        fb2content = fb2content.replace("&mdash;", "-")

        fb2content = fb2content.replace("&quot;", "\"")
        fb2content = fb2content.replace("&nbsp;", " ")
        #fb2content = fb2content.replace("&lt;", "[")
        #fb2content = fb2content.replace("&gt;", "]")    
        fb2content = fb2content.replace("&laquo;", "\"")    
        fb2content = fb2content.replace("&raquo;", "\"")
        fb2content = fb2content.replace("<blockquote>", "<cite><p>")
        fb2content = fb2content.replace("</blockquote>", "</p></cite>")
        fb2content = fb2content.replace("&", "&amp;")
        # print "done"
        log.debug("HTML tags processing finished")
        return fb2content
        
    def download_image(self, i, url, digest, basket, counter_lock, lock):
    
        log.debug("ImageDownloader %d: Getting image from %s", i, url)
    
        url = re.sub('&amp;', '&', url)
    
        try:
            image = Image.Image()
            image = Image.open(StringIO.StringIO(urllib2.urlopen(url).read()))
        except IOError, e:
            log.error("cannot get image content %s. %s", url, e)
        except socket.timeout:
            log.error("Timeout = cannot get image content %s", url)
        else:
            image = ImageOps.grayscale(image)
            image = ImageOps.posterize(image, 2)    # 4 gray levels
            content = StringIO.StringIO()
            image.save(content, 'JPEG')
            image_data = content.getvalue()
            block = base64.b64encode(image_data)
            lock.acquire()
            basket[digest] = block
            lock.release()
        finally:
            counter_lock.acquire()
            self.counter += 1
            counter_lock.release()
    
    #download_image = staticmethod(download_image)

    def imagesToSections(self, hash_to_url_dic):

        log.debug("Converting images to sections. URLs to get/convert: %d", len(hash_to_url_dic))

        content = ""
        lock = thread.allocate_lock()
        counter_lock = thread.allocate_lock()
        basket = dict()
        self.counter = 0
        i = 0
        for digest, url in hash_to_url_dic.items():
            thread.start_new_thread(self.download_image, (i, url, digest, basket, counter_lock, lock))
            i += 1
     
        while self.counter < len(hash_to_url_dic): pass
            
        for digest, block in basket.items():
            binaryblock = '<binary id="%s">' % digest
            binaryblock += block
            binaryblock += '</binary>'
            content += binaryblock
            
        return content

    def write_book_file(self, dirname, filetitle, content, zip_enabled = 1):
        """ Writing book content to file - plain fb2 or zipped fb2"""

        filetitle = re.sub('[/\\><~:%*\s]', '-', filetitle)
        filetitle = filetitle.replace('"', '')
        
        filetitle = filetitle.encode('utf-8')
        content = content.encode('utf-8')
        
        filetitle = md5.new(str(time.time())).hexdigest() + '_' + filetitle + '.fb2'
        fullfilename = os.path.join(dirname, filetitle)
        
        if zip_enabled:
            fullfilename += ".zip"
            arch = zipfile.ZipFile(fullfilename, 'w', zipfile.ZIP_DEFLATED)
            arch.writestr(filetitle, content)
            arch.close()
        else:
            file = open(fullfilename, "w")
            file.write(content)
            file.close()
        log.info("Book file saved as %s", fullfilename)
        return fullfilename

